package com.flask.colorpicker.cameralibrary;
import com.flask.colorpicker.cameralibrary.listener.CaptureListener;
import com.flask.colorpicker.cameralibrary.listener.ClickListener;
import com.flask.colorpicker.cameralibrary.listener.ErrorListener;
import com.flask.colorpicker.cameralibrary.listener.IImageArrivalListenerImpl;
import com.flask.colorpicker.cameralibrary.listener.JCameraListener;
import com.flask.colorpicker.cameralibrary.listener.TypeListener;
import com.flask.colorpicker.cameralibrary.state.CameraMachine;
import com.flask.colorpicker.cameralibrary.util.CheckPermission;
import com.flask.colorpicker.cameralibrary.util.FileUtil;
import com.flask.colorpicker.cameralibrary.util.LogUtil;
import com.flask.colorpicker.cameralibrary.util.NumCalcUtil;
import com.flask.colorpicker.cameralibrary.util.ScreenUtils;
import com.flask.colorpicker.cameralibrary.view.CameraView;
import ohos.agp.animation.AnimatorGroup;
import ohos.agp.animation.AnimatorProperty;

import ohos.agp.components.StackLayout;
import ohos.agp.components.Component;
import ohos.agp.components.Image;
import ohos.agp.components.AttrSet;
import ohos.agp.components.LayoutScatter;
import ohos.agp.components.ComponentContainer;

import ohos.agp.components.element.Element;
import ohos.agp.components.surfaceprovider.SurfaceProvider;
import ohos.agp.graphics.SurfaceOps;
import ohos.agp.window.service.DisplayAttributes;
import ohos.agp.window.service.DisplayManager;
import ohos.agp.window.service.WindowManager;
import ohos.app.Context;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.global.configuration.Configuration;
import ohos.media.camera.params.Metadata;
import ohos.media.image.ImageReceiver;
import ohos.media.image.PixelMap;
import ohos.media.image.common.ImageFormat;
import ohos.media.player.Player;
import ohos.multimodalinput.event.TouchEvent;
import ohos.utils.net.Uri;

import java.util.Optional;

public class JCameraView extends StackLayout
        implements CameraInterface.CameraOpenOverCallback,
        SurfaceOps.Callback,
        CameraView,
        Component.TouchEventListener {
    /**
     * 拍照浏览时候的类型
     */
    public static final int TYPE_PICTURE = 0x001;
    public static final int TYPE_VIDEO = 0x002;
    public static final int TYPE_SHORT = 0x003;
    public static final int TYPE_DEFAULT = 0x004;

    public static final int BUTTON_STATE_ONLY_CAPTURE = 0x101; // 只能拍照

    private static final int IMAGE_RCV_CAPACITY = 5;

    // Camera状态机
    private CameraMachine mMachine;
    // 回调监听
    private JCameraListener mJCameraListener;
    private ClickListener mLeftClickListener;
    private ClickListener mRightClickListener;
    private EventHandler mHandler;
    private final Context mContext;
    private StackLayout mVideoView;
    private Image mPhoto;
    private Image mSwitchCamera;
    private CaptureLayout mCaptureLayout;
    private FocusView mFocusView;

    private int mLayout_width;
    private float mScreenProp = 0f;

    private PixelMap mCaptureBitmap; // 捕获的图片
    private int mIconLeft = 0; // 左图标
    private int mIconRight = 0; // 右图标
    // 缩放梯度
    private int mZoomGradient = 0;
    private float mFirstTouchLength = 0;
    private SurfaceProvider mProvider;
    private ImageReceiver mImageReceiver;

    private boolean isFirstTouch = true;
    private boolean isPlay = false;
    private boolean isCameraDestroyed = false;
    private ErrorListener mErrorListener;

    /**
     * 设置自定义图标
     *
     * @param iconLeft  int
     * @param iconRight int
     */
    public void setIconLeftAndRight(int iconLeft, int iconRight) {
        this.mIconLeft = iconLeft;
        this.mIconRight = iconRight;
        if (mCaptureLayout != null) {
            mCaptureLayout.setIconSrc(iconLeft, iconRight);
        }
    }

    /**
     * 设置自定义图标
     *
     * @param leftElement  Element
     * @param rightElement Element
     */
    public void setIconLeftAndRight(Element leftElement, Element rightElement) {
        if (mCaptureLayout != null) {
            mCaptureLayout.setIconSrc(leftElement, rightElement);
        }
    }

    public JCameraView(Context context, AttrSet attrs) {
        super(context, attrs);
        mContext = context;
        if (CheckPermission.checkPermission(mContext)) {
            initData();
            initView();
        }
    }

    private void initData() {
        mLayout_width = ScreenUtils.getScreenWidth(mContext);
        float screenHeight = ScreenUtils.getScreenHeight(mContext);

        if (mScreenProp == 0) {
            mScreenProp = screenHeight / mLayout_width;
        }
        LogUtil.error(LogUtil.DEFAULT_TAG, "screenProp:" + mScreenProp);

        mImageReceiver =
                ImageReceiver.create(
                        mLayout_width, ScreenUtils.getScreenHeight(mContext), ImageFormat.JPEG, IMAGE_RCV_CAPACITY);

        // 缩放梯度
        mZoomGradient = (int) (mLayout_width / 16f);
        mMachine = new CameraMachine(getContext(), this);

        mImageReceiver.setImageArrivalListener(new IImageArrivalListenerImpl(mMachine));
    }

    private void initView() {
        Component view = LayoutScatter.getInstance(mContext).parse(ResourceTable.Layout_camera_view, this, true);
        mVideoView = (StackLayout) view.findComponentById(ResourceTable.Id_video_preview);
        initSurface();
        mPhoto = (Image) view.findComponentById(ResourceTable.Id_image_photo);
        mSwitchCamera = (Image) view.findComponentById(ResourceTable.Id_image_switch);
        setFlashRes();
        mCaptureLayout = (com.flask.colorpicker.cameralibrary.CaptureLayout) view.findComponentById(ResourceTable.Id_capture_layout);
        mCaptureLayout.setIconSrc(mIconLeft, mIconRight);
        mFocusView = (FocusView) view.findComponentById(ResourceTable.Id_fouce_view);
        reqCaptureAndFocusSize();
        // 切换摄像头
        mSwitchCamera.setClickedListener(component -> mMachine.switchCamera(getVideoHolder(), mScreenProp));
        addCaptureListener();
        addConfirmOrCancel();
        mCaptureLayout.setLeftClickListener(
                () -> {
                    if (mLeftClickListener != null) {
                        mLeftClickListener.onClick();
                    }
                });
        mCaptureLayout.setRightClickListener(
                () -> {
                    if (mRightClickListener != null) {
                        mRightClickListener.onClick();
                    }
                });
        LogUtil.error(LogUtil.DEFAULT_TAG, "initView end");
    }

    private void addConfirmOrCancel() {
        if (mCaptureLayout == null) {
            return;
        }
        // 确认 取消
        mCaptureLayout.setTypeListener(
                new TypeListener() {
                    @Override
                    public void cancel(boolean cancel) {
                        if (cancel) {
                            CameraInterface.getInstance().reOpenCamera(mMachine);
                        }
                        mMachine.cancel(getVideoHolder(), mScreenProp);
                    }

                    @Override
                    public void confirm(boolean confirm) {
                        if (confirm) {
                            CameraInterface.getInstance().reOpenCamera(mMachine);
                        }
                        mMachine.confirm();
                    }
                });
    }

    private void addCaptureListener() {
        if (mCaptureLayout == null) {
            return;
        }
        // 拍照
        mCaptureLayout.setCaptureListener(
                new CaptureListener() {
                    @Override
                    public void takePictures() {
                        LogUtil.info(LogUtil.DEFAULT_TAG, "click takePictures");
                        mSwitchCamera.setVisibility(INVISIBLE);
                        mMachine.capture();
                    }

                    @Override
                    public void recordStart() {
                        mSwitchCamera.setVisibility(INVISIBLE);
                        mMachine.record();
                    }

                    @Override
                    public void recordShort(final long time) {
                        mSwitchCamera.setVisibility(VISIBLE);
                        postDelayed(() -> mMachine.stopRecord(true, time), 1500 - time);
                    }

                    @Override
                    public void recordEnd(long time) {
                        mMachine.stopRecord(false, time);
                    }

                    @Override
                    public void recordZoom(float zoom) {
                        mMachine.zoom(zoom, CameraInterface.TYPE_RECORDER);
                    }

                    @Override
                    public void onError(String errorMessage, int type) {
                        if (mErrorListener != null) {
                            mErrorListener.onError(errorMessage, type);
                        }
                    }
                });
    }

    private void reqCaptureAndFocusSize() {
        DisplayAttributes displayAttributes =
                DisplayManager.getInstance().getDefaultDisplay(getContext()).get().getAttributes();
        int screenWidth = displayAttributes.width;
        int layoutWidth;
        if (getResourceManager().getConfiguration().direction == Configuration.DIRECTION_VERTICAL) {
            layoutWidth = screenWidth;
        } else {
            layoutWidth = screenWidth / 2;
        }
        int buttonSize = (int) (layoutWidth / 4.5f);
        int layoutHeight = buttonSize + (buttonSize / 5) * 2 + 100;
        LayoutConfig config = (LayoutConfig) mCaptureLayout.getLayoutConfig();
        config.height = layoutHeight;
        config.width = layoutWidth;
        mCaptureLayout.setLayoutConfig(config);

        LayoutConfig focusViewLayoutConfig = (LayoutConfig) mFocusView.getLayoutConfig();
        focusViewLayoutConfig.width = screenWidth / 3;
        focusViewLayoutConfig.height = screenWidth / 3;
        mFocusView.setLayoutConfig(focusViewLayoutConfig);
    }

    private void postDelayed(Runnable runnable, long delayMills) {
        if (mHandler == null) {
            mHandler = new EventHandler(EventRunner.getMainEventRunner());
        }
        mHandler.postTask(runnable, delayMills);
    }

    @Override
    public void cameraHasOpened() {
        CameraInterface.getInstance().doStartPreview(getVideoHolder(), mScreenProp);
    }

    @Override
    public SurfaceProvider getSurfaceProvider() {
        return mProvider;
    }

    @Override
    public ImageReceiver getImageReceiver() {
        return mImageReceiver;
    }

    private void reInitStatus() {
        resetState(TYPE_DEFAULT); // 重置状态
        CameraInterface.getInstance().registerSensorManager();
        mMachine.start(getVideoHolder(), mScreenProp);
    }

    // 生命周期onResume
    public void onResume() {
        LogUtil.info(LogUtil.DEFAULT_TAG, "JCameraView onResume,isCameraDestroyed:" + isCameraDestroyed);
        if (isCameraDestroyed) {
            initSurface();
        } else {
            reInitStatus();
        }
    }

    // 生命周期onPause
    public void onPause() {
        LogUtil.info(LogUtil.DEFAULT_TAG, "JCameraView onPause");
        stopVideo();
        resetState(TYPE_PICTURE);
        CameraInterface.getInstance().isPreview(true);
        CameraInterface.getInstance().unregisterSensorManager();
    }

    private void onDestroyed() {
        isCameraDestroyed = true;
        isPlay = false;
        stopVideo();
        resetState(TYPE_PICTURE);
        CameraInterface.getInstance().isPreview(false);
        CameraInterface.getInstance().unregisterSensorManager();
        CameraInterface.getInstance().doDestroyCamera();
    }

    @Override
    public void surfaceCreated(SurfaceOps surfaceOps) {
        LogUtil.info(LogUtil.DEFAULT_TAG, "JCameraView SurfaceCreated isPlay:" + isPlay);
        CameraInterface.getInstance().doOpenCamera(JCameraView.this, mMachine);
        if (isCameraDestroyed) {
            reInitStatus();
        }
        isCameraDestroyed = false;
    }

    @Override
    public void surfaceChanged(SurfaceOps surfaceOps, int i, int i1, int i2) {
    }

    @Override
    public void surfaceDestroyed(SurfaceOps surfaceOps) {
        LogUtil.info(LogUtil.DEFAULT_TAG, "JCameraView SurfaceDestroyed");
        onDestroyed();
    }

    @Override
    public boolean onTouchEvent(Component component, TouchEvent event) {
        switch (event.getAction()) {
            case TouchEvent.PRIMARY_POINT_DOWN:
                if (event.getPointerCount() == 1) {
                    // 显示对焦指示器
                    float eventX = event.getPointerPosition(0).getX();
                    float eventY = event.getPointerPosition(0).getY();
                    setFocusViewWidthAnimation(eventX, eventY);
                }
                break;
            case TouchEvent.POINT_MOVE:
                if (event.getPointerCount() == 1) {
                    isFirstTouch = true;
                }
                if (event.getPointerCount() == 2) {
                    float point1X = event.getPointerPosition(0).getX();
                    float point1Y = event.getPointerPosition(0).getY();

                    float point2X = event.getPointerPosition(1).getX();
                    float point2Y = event.getPointerPosition(1).getY();

                    float result =
                            (float) Math.sqrt(Math.pow(NumCalcUtil.subtract(point1X , point2X), 2)
                                    + Math.pow(NumCalcUtil.subtract(point1Y , point2Y), 2));

                    if (isFirstTouch) {
                        mFirstTouchLength = result;
                        isFirstTouch = false;
                    }
                    if ((int) (NumCalcUtil.subtract(result , mFirstTouchLength)) / mZoomGradient != 0) {
                        isFirstTouch = true;
                        mMachine.zoom(NumCalcUtil.subtract(result , mFirstTouchLength), CameraInterface.TYPE_CAPTURE);
                    }
                }
                break;
            case TouchEvent.PRIMARY_POINT_UP:
                isFirstTouch = true;
                break;
        }
        return true;
    }

    // 对焦框指示器动画
    private void setFocusViewWidthAnimation(float xx, float yy) {
        mMachine.focus(xx, yy);
    }

    /**
     * set SaveVideo Path
     *
     * @param path String
     */
    public void setSaveVideoPath(String path) {
        CameraInterface.getInstance().setSaveVideoPath(path);
    }

    /**
     * set JCameraListener
     *
     * @param jCameraListener JCameraListener
     */
    public void setJCameraListener(JCameraListener jCameraListener) {
        this.mJCameraListener = jCameraListener;
    }

    /**
     * 启动Camera错误回调
     *
     * @param errorListener ErrorListener
     */
    public void setErrorListener(ErrorListener errorListener) {
        this.mErrorListener = errorListener;
        CameraInterface.getInstance().setErrorListener(errorListener);
    }

    @Override
    public void resetState(int type) {
        if (mVideoView == null) {
            return;
        }
        switch (type) {
            case TYPE_VIDEO:
                stopVideo(); // 停止播放
                mVideoView.setLayoutConfig(
                        new StackLayout.LayoutConfig(LayoutConfig.MATCH_PARENT, LayoutConfig.MATCH_PARENT));
                mMachine.start(getVideoHolder(), mScreenProp);
                break;
            case TYPE_PICTURE:
                mPhoto.setVisibility(INVISIBLE);
                break;
            case TYPE_SHORT:
                break;
            case TYPE_DEFAULT:
                mVideoView.setLayoutConfig(
                        new StackLayout.LayoutConfig(LayoutConfig.MATCH_PARENT, LayoutConfig.MATCH_PARENT));
                break;
        }
        mSwitchCamera.setVisibility(VISIBLE);
        mCaptureLayout.resetCaptureLayout();
    }

    @Override
    public void confirmState(int type) {
        LogUtil.error(LogUtil.DEFAULT_TAG, "confirmState type:" + type);
        switch (type) {
            case TYPE_PICTURE:
                mPhoto.setVisibility(INVISIBLE);
                if (mJCameraListener != null) {
                    Uri uri = FileUtil.saveImageToPicture(getContext(),"JCamera", mCaptureBitmap);
                    mJCameraListener.captureSuccess(mCaptureBitmap,uri+"");
                }
                break;
            case TYPE_SHORT:
            case TYPE_DEFAULT:
                break;
        }
        mCaptureLayout.resetCaptureLayout();
    }

    @Override
    public void showPicture(PixelMap bitmap, boolean isVertical) {
        if (isVertical) {
            mPhoto.setScaleMode(Image.ScaleMode.STRETCH);
        } else {
            mPhoto.setScaleMode(Image.ScaleMode.ZOOM_CENTER);
        }
        mCaptureBitmap = bitmap;
        mPhoto.setPixelMap(bitmap);
        mPhoto.setVisibility(VISIBLE);
        mCaptureLayout.startAlphaAnimation();
        mCaptureLayout.startTypeBtnAnimator();
    }

    private void initSurface() {
        if (mVideoView != null && mVideoView.getChildCount() > 0) {
            mVideoView.removeAllComponents();
        }
        if (mProvider != null) {
            mProvider.release();
            mProvider = null;
        }
        mProvider = new SurfaceProvider(mContext);
        StackLayout.LayoutConfig params =
                new StackLayout.LayoutConfig(
                        ComponentContainer.LayoutConfig.MATCH_PARENT, ComponentContainer.LayoutConfig.MATCH_PARENT);

        mProvider.setLayoutConfig(params);
        mProvider.pinToZTop(false);
        WindowManager.getInstance().getTopWindow().get().setTransparent(true);
        mProvider.getSurfaceOps().get().addCallback(this);

        if (mVideoView != null) {
            mVideoView.addComponent(mProvider);
        }
        LogUtil.info(LogUtil.DEFAULT_TAG, "initSurface success");
    }

    private SurfaceOps getVideoHolder() {
        Component component = mVideoView.getComponentAt(0);
        if (component instanceof SurfaceProvider) {
            SurfaceProvider surfaceProvider = (SurfaceProvider) component;
            Optional<SurfaceOps> surfaceOps = surfaceProvider.getSurfaceOps();
            return surfaceOps.orElse(null);
        }
        return null;
    }

    @Override
    public void stopVideo() {
    }

    @Override
    public void handlerFoucs(float pointX, float pointY) {
        if (pointY > mCaptureLayout.getTop()) {
            return;
        }
        mFocusView.setVisibility(VISIBLE);
        if (pointX < mFocusView.getWidth() / 2f) {
            pointX = mFocusView.getWidth() / 2f;
        }
        if (pointX > NumCalcUtil.subtract(mLayout_width , mFocusView.getWidth() / 2f)) {
            pointX = NumCalcUtil.subtract(mLayout_width , mFocusView.getWidth() / 2f);
        }
        if (pointY < mFocusView.getWidth() / 2f) {
            pointY = mFocusView.getWidth() / 2f;
        }
        if (pointY > NumCalcUtil.subtract(mCaptureLayout.getTop() , mFocusView.getWidth() / 2f)) {
            pointY = NumCalcUtil.subtract(mCaptureLayout.getTop() , mFocusView.getWidth() / 2f);
        }
        mFocusView.setX(NumCalcUtil.subtract(pointX , mFocusView.getWidth() / 2f));
        mFocusView.setY(NumCalcUtil.subtract(pointY , mFocusView.getHeight() / 2f));

        AnimatorProperty scaleAnimation = new AnimatorProperty(mFocusView);
        scaleAnimation.scaleX(1f).scaleX(0.6f);
        scaleAnimation.scaleY(1f).scaleY(0.6f);

        AnimatorProperty alphaAnimation = new AnimatorProperty(mFocusView);
        alphaAnimation.alpha(1f).alpha(0.4f).alpha(1f).alpha(0.4f).alpha(1f).alpha(0.4f).alpha(1f);

        AnimatorGroup animatorGroup = new AnimatorGroup();
        animatorGroup.runParallel(scaleAnimation, alphaAnimation);
        animatorGroup.setDuration(400);
        animatorGroup.start();
    }

    /**
     * set LeftClick Listener
     *
     * @param clickListener ClickListener
     */
    public void setLeftClickListener(ClickListener clickListener) {
        this.mLeftClickListener = clickListener;
    }

    /**
     * set RightClick Listener
     *
     * @param clickListener ClickListener
     */
    public void setRightClickListener(ClickListener clickListener) {
        this.mRightClickListener = clickListener;
    }

    private void setFlashRes() {
        mMachine.flash(Metadata.FlashMode.FLASH_CLOSE);
        LogUtil.error(LogUtil.DEFAULT_TAG, "setFlashRes end");
    }
}
